package com.cyberx.slackspringbootstarter.util;

import com.cyberx.slackspringbootstarter.model.table.Align;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.Getter;

public class ConsoleTable {
  private ConsoleTable() {}

  /**
   * 控制台输出表格信息。
   *
   * <p>注意：header.length == data[n].length。
   *
   * @param tableName 表名
   * @param header 表头
   * @param data 表数据
   * @return 控制台表格信息
   */
  public static String of(String tableName, String[] header, String[][] data) {
    Table table = new ConsoleTable.Table().name(tableName).header(header).body(data);
    return table.render();
  }

  static class Table {
    private String name;
    private String[] headers;
    private String[][] rows;

    /** 每列最大宽度 */
    private int[] colMaxWidthArray;

    public Table name(String name) {
      this.name = name;
      return this;
    }

    /**
     * 设定表头。
     *
     * @param headers 表头信息
     * @return Table实例
     */
    public Table header(String[] headers) {
      this.headers = headers;
      calculateCellMaxWidthArray(this.headers);
      return this;
    }

    /**
     * 填充行信息。
     *
     * @param rows 行信息
     * @return Table实例
     */
    public Table body(String[][] rows) {
      this.rows = rows;
      for (String[] row : rows) {
        calculateCellMaxWidthArray(row);
      }
      return this;
    }

    public String render() {
      StringBuilder sb = new StringBuilder();
      sb.append(Objects.toString(this.name, "Table")).append("\n");
      fillBorder(sb, Style.TOP);
      fillRow(sb, Style.CELL, Align.RIGHT, this.headers);
      fillBorder(sb, Style.BOTTOM);
      fillRows(sb, Align.RIGHT, this.rows);
      fillBorder(sb, Style.BOTTOM);
      return sb.toString();
    }

    private void calculateCellMaxWidthArray(String[] row) {
      if (row == null) {
        return;
      }
      if (colMaxWidthArray == null) {
        colMaxWidthArray = new int[row.length];
      }
      for (int i = 0; i < colMaxWidthArray.length; i++) {
        String cell = getCellValue(i, row);
        // 每个cell左右两边新增一个空格
        int width = StringUtils.consoleLength(cell) + 2;
        colMaxWidthArray[i] = Math.max(width, colMaxWidthArray[i]);
      }
    }

    /**
     * 拼装边框
     *
     * @param sb StringBuilder
     */
    private void fillBorder(StringBuilder sb, Style style) {
      Symbol symbol = style.symbol;
      sb.append(symbol.begin);
      List<String> content = new ArrayList<>();
      for (int width : colMaxWidthArray) {
        String repeat = StringUtils.repeat(symbol.join + "", width);
        content.add(repeat);
      }
      String line = String.join(symbol.spilt + "", content);
      sb.append(line);
      sb.append(symbol.end);
      sb.append("\n");
    }

    /**
     * 拼装边框
     *
     * @param sb StringBuilder
     */
    private void fillRows(StringBuilder sb, Align align, String[][] rows) {
      for (String[] row : rows) {
        fillRow(sb, Style.CELL, align, row);
      }
    }

    /**
     * 拼装边框
     *
     * @param sb StringBuilder
     */
    private void fillRow(StringBuilder sb, Style style, Align align, String[] row) {
      if (row == null) {
        return;
      }
      Symbol symbol = style.symbol;
      sb.append(symbol.begin);
      List<String> content = new ArrayList<>();
      for (int i = 0; i < colMaxWidthArray.length; i++) {
        String cell = getCellValue(i, row);
        int cellMaxWidth = colMaxWidthArray[i];
        String string = StringUtils.padding(cell, cellMaxWidth, symbol.join, align);
        content.add(string);
      }
      String line = String.join(symbol.spilt + "", content);
      sb.append(line);
      sb.append(symbol.end);
      sb.append("\n");
    }

    private String getCellValue(int rowIndex, String[] row) {
      // 如果cell为空，则默认填充空字符串
      String cell;
      if (rowIndex >= row.length) {
        cell = "";
      } else {
        cell = row[rowIndex];
      }
      return cell;
    }
  }

  /** 表格样式 */
  enum Style {
    TOP(new Symbol('+', '-', '+', '+')),
    CELL(new Symbol('|', ' ', '|', '|')),
    MIDDLE(new Symbol('+', '-', '+', '+')),
    BOTTOM(new Symbol('+', '-', '+', '+')),
    CROSS_TOP(new Symbol('+', '-', '+', '+')),
    CROSS_BOTTOM(new Symbol('+', '-', '+', '+'));
    ;

    final Symbol symbol;

    Style(Symbol symbol) {
      this.symbol = symbol;
    }
  }

  @Getter
  public static class Symbol {
    /** 起始字符 */
    char begin;

    /** 填充字符 */
    char join;

    /** 分割字符 */
    char spilt;

    /** 结束字符 */
    char end;

    /**
     * 构造方法。
     *
     * @param begin 起始字符
     * @param end 结束字符
     * @param spilt 分割字符
     * @param join 填充字符
     */
    Symbol(char begin, char join, char spilt, char end) {
      this.begin = begin;
      this.end = end;
      this.spilt = spilt;
      this.join = join;
    }
  }
}
